/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.velocityme.session;

import com.velocityme.entity.ContactDetailType;
import com.velocityme.entity.ContactDetailTypes;
import com.velocityme.entity.Department;
import com.velocityme.entity.Directory;
import com.velocityme.entity.EncodedLicence;
import com.velocityme.entity.LicenceManagement;
import com.velocityme.entity.LoginHistory;
import com.velocityme.entity.Node;
import com.velocityme.entity.Organisation;
import com.velocityme.entity.Person;
import com.velocityme.entity.Role;
import com.velocityme.entity.RootNode;
import com.velocityme.entity.State;
import com.velocityme.entity.Status;
import com.velocityme.entity.SystemNode;
import com.velocityme.entity.SystemProperties;
import com.velocityme.entity.SystemProperty;
import com.velocityme.entity.TaskStateMachine;
import com.velocityme.entity.Transition;
import com.velocityme.entity.User;
import com.velocityme.entity.UserAccess;
import com.velocityme.enums.NotificationMechanism;
import com.velocityme.enums.Permission;
import com.velocityme.utility.PasswordEncrypter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.naming.ServiceUnavailableException;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;

/**
 *
 * @author rcrida
 */
@Stateless
public class SetupBean implements SetupLocal {

    @PersistenceContext
    private EntityManager em;

    @EJB
    private KeyLocal m_keyLocal;

    @EJB
    private NodeLocal m_nodeLocal;

    @EJB
    private StateLocal m_stateLocal;

    @EJB
    private StatusLocal m_statusLocal;

    @EJB
    private TaskStateMachineLocal m_taskStateMachineLocal;

    @EJB
    private TreeLocal m_treeLocal;

    /**
     * Check if the database has already been initialised
     */
    public boolean isLicenceInstalled() {
        Collection<EncodedLicence> licenceKeys = (List<EncodedLicence>)em.createNamedQuery("findAllLicenceKeys").getResultList();
        if (licenceKeys.size() > 0)
            return true;
        else {
            // make sure that the client GUID exists
            try {
                em.createNamedQuery("findSystemPropertyByName").setParameter("name", "client.guid").getSingleResult();
            } catch(NoResultException e) {
                // we didn't find it so create one
                SystemProperty clientGuid = new SystemProperty("client.guid", UUID.randomUUID().toString(), false, false);
                em.persist(clientGuid);
            }
            return true;
            // FIXME: above line should be return false;
        }
    }
    
    public void setupDatabase(String username, String password) {
        DefaultNodes nodes = setupSystemNew(username, password);
        if (nodes != null) {
            login(username, password);
            setupProjects(nodes.rootNode);
            setupSystemConfig(nodes.rootNode, nodes.defaultOrganisation, nodes.defaultDepartment, nodes.defaultPerson, nodes.defaultUser, nodes.defaultRole, nodes.directoryNode);
            logout();
        }
    }

    /**
     * Creates the root node containing default role and default user assigned to the role.
     * Also sets up the permissions.
     *
     * @param p_username Default username
     * @param p_password Default setup user password
     **/
    @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW)
    public DefaultNodes setupSystemNew(final String username, final String password) {
        try {
            // if root already exists then exit
            try {
                em.createNamedQuery("findRootNode").getSingleResult();
                // if the root exists then we mustn't do anything
                return null;
            } catch (NoResultException e) {
                // this is thrown if the root doesn't exist, we must continue and populate
            }
            DefaultNodes nodes = new DefaultNodes();
            // create root
            nodes.rootNode = new RootNode();
            em.persist(nodes.rootNode);
            // create directory
            nodes.directoryNode = new Directory(nodes.rootNode);
            em.persist(nodes.directoryNode);
            // create default organisation
            nodes.defaultOrganisation = new Organisation(nodes.rootNode, "Default Organisation", "Default organisation");
            em.persist(nodes.defaultOrganisation);
            // create default department
            nodes.defaultDepartment = new Department(nodes.rootNode, "Default Department", "Default department");
            em.persist(nodes.defaultDepartment);
            // create system default person
            nodes.defaultPerson = new Person(nodes.rootNode, "Default", "Person", "Default person used for database setup");
            em.persist(nodes.defaultPerson);
            // create system default user
            nodes.defaultUser = new User(nodes.rootNode, username, "Default user used for database setup", nodes.defaultPerson, true, true);
            nodes.defaultUser.setPasswordHash(PasswordEncrypter.getInstance().encrypt(password));
            em.persist(nodes.defaultUser);
            // generate default contact detail types
            ContactDetailType contactDetailType = new ContactDetailType(NotificationMechanism.EMAIL, "Email", true, true, 0);
            em.persist(contactDetailType);
            // generate permission set for system setup role
            HashSet<Permission> permissions = new HashSet<Permission>();
            permissions.add(Permission.DEPARTMENT_CREATE);
            permissions.add(Permission.DEPARTMENT_EDIT);
            permissions.add(Permission.ORGANISATION_CREATE);
            permissions.add(Permission.ORGANISATION_EDIT);
            permissions.add(Permission.PERSON_CREATE);
            permissions.add(Permission.PERSON_EDIT);
            permissions.add(Permission.USER_CREATE);
            permissions.add(Permission.USER_EDIT);
            permissions.add(Permission.USER_ENABLE);
            permissions.add(Permission.USER_CHANGE_PASSWORD);
            permissions.add(Permission.GROUP_CREATE);
            permissions.add(Permission.GROUP_EDIT);
            permissions.add(Permission.CONTACT_DETAIL_TYPE_EDIT);
            permissions.add(Permission.ROLE_CREATE);
            permissions.add(Permission.ROLE_EDIT);
            permissions.add(Permission.USER_ACCESS_CREATE);
            permissions.add(Permission.USER_ACCESS_DELETE);
            permissions.add(Permission.GROUP_ACCESS_CREATE);
            permissions.add(Permission.GROUP_ACCESS_DELETE);
            permissions.add(Permission.ACTION_CREATE);
            permissions.add(Permission.ACTION_EDIT);
            permissions.add(Permission.NODE_CREATE);
            permissions.add(Permission.NODE_EDIT);
            permissions.add(Permission.NODE_DELETE);
            permissions.add(Permission.TASK_CHANGE_STATE);
            permissions.add(Permission.PROJECT_CREATE);
            permissions.add(Permission.PROJECT_EDIT);
            permissions.add(Permission.SPRINT_CREATE);
            permissions.add(Permission.SPRINT_EDIT);
            permissions.add(Permission.TASK_TYPE_CREATE);
            permissions.add(Permission.TASK_TYPE_EDIT);
            permissions.add(Permission.STATE_CREATE);
            permissions.add(Permission.STATE_EDIT);
            permissions.add(Permission.STATUS_CREATE);
            permissions.add(Permission.STATUS_EDIT);
            permissions.add(Permission.ISSUE_CREATE);
            permissions.add(Permission.NODE_VIEW);
            permissions.add(Permission.SYSTEM_PROPERTY_EDIT);

            for (Permission permission: Permission.values())
                permissions.add(permission);
            
            // create default setup role
            nodes.defaultRole = new Role(nodes.rootNode, "Default Setup", "Role used for setting up the database", permissions, true);
            em.persist(nodes.defaultRole);
            // create default system properties
            em.persist(new SystemProperty("email.from.address", "you@your.address", false, true));
            em.persist(new SystemProperty("email.internet.url", "https://internet.server.address:443", false, true));
            em.persist(new SystemProperty("email.intranet.url", "http://intranet.server.address", false, true));
            em.persist(new SystemProperty("email.account", "", false, true));
            em.persist(new SystemProperty("email.account.username", "username", false, true));
            em.persist(new SystemProperty("email.account.password", "password", true, true));
            em.persist(new SystemProperty("file.attachment.home", "./", false, true));
            em.persist(new SystemProperty("owner.organisation", "Your organisation", false, true));
            em.persist(new SystemProperty("owner.organisation.url", "http://organisation.web.address", false, true));
            setupUserAccess(nodes.rootNode, nodes.defaultUser, nodes.defaultRole);
            return nodes;
        } catch (ServiceUnavailableException ex) {
            Logger.getLogger(SetupBean.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }
    // Add business logic below. (Right-click in editor and choose
    // "Insert Code > Add Business Method" or "Web Service > Add Operation")

    private void setupUserAccess(RootNode rootNode, User user, Role role) {
        // assign user to this role in root node
        em.persist(new UserAccess(rootNode, user, role));
        em.merge(rootNode);
        em.merge(user);
        em.merge(role);
    }

    private boolean login(String username, String password) {
        return m_keyLocal.login(username, password, "SetupDatabase") == KeyLocal.Success.SUCCESS;
    }

    private void logout() {
        m_keyLocal.logout();
    }

    private void setupProjects(RootNode rootNode) {
        SystemNode projectNode = new SystemNode(rootNode, "Project", "The root node for all projects");
        m_nodeLocal.createNode(m_keyLocal, projectNode);
    }

    private void setupSystemConfig(RootNode rootNode,
            Organisation defaultOrganisation,
            Department defaultDepartment,
            Person defaultPerson,
            User defaultUser,
            Role defaultRole,
            Node directoryNode) {
        SystemNode systemConfigNode = new SystemNode(rootNode, "System Configuration", "Branch of tree for system configuration");
        m_nodeLocal.createNode(m_keyLocal, systemConfigNode);
        setupOrganisations(systemConfigNode, defaultOrganisation, defaultDepartment, defaultPerson);
        setupUsers(systemConfigNode, defaultUser);
        setupGroups(systemConfigNode);
        setupTaskTypes(systemConfigNode);
        setupRoles(systemConfigNode, defaultRole);
        setupContactDetailTypes(systemConfigNode);
        setupSystemProperties(systemConfigNode);
        setupLicenceManagement(systemConfigNode);
        setupLoginHistory(systemConfigNode);
        setupDirectory(systemConfigNode, directoryNode);
    }

    private void setupOrganisations(Node systemConfigNode, Organisation defaultOrganisation, Department defaultDepartment, Person defaultPerson) {
        SystemNode organisationNode = new SystemNode(systemConfigNode, "Setup Organisations", "The root node for all organisations");
        m_nodeLocal.createNode(m_keyLocal, organisationNode);

        // move default organisation to the correct place
        m_treeLocal.moveChildNode(m_keyLocal, organisationNode, defaultOrganisation);
        
        // move default department to the correct place
        m_treeLocal.moveChildNode(m_keyLocal, defaultOrganisation, defaultDepartment);

        // move default person to the correct place
        m_treeLocal.moveChildNode(m_keyLocal, defaultDepartment, defaultPerson);
    }

    private void setupUsers(Node systemConfigNode, User defaultUser) {
        SystemNode userNode = new SystemNode(systemConfigNode, "Setup Users", "The root node for all users");
        m_nodeLocal.createNode(m_keyLocal, userNode);

        // move default user to the correct place
        m_treeLocal.moveChildNode(m_keyLocal, userNode, defaultUser);
    }

    private void setupGroups(Node systemConfigNode) {
        SystemNode groupNode = new SystemNode(systemConfigNode, "Setup Groups", "The root node for all groups");
        m_nodeLocal.createNode(m_keyLocal, groupNode);
    }

    private void setupRoles(Node systemConfigNode, Role defaultRole) {
        SystemNode roleNode = new SystemNode(systemConfigNode, "Setup Roles", "The root node for all roles");
        m_nodeLocal.createNode(m_keyLocal, roleNode);

        // move default role to the correct place
        m_treeLocal.moveChildNode(m_keyLocal, roleNode, defaultRole);
    }

    public void setupTaskTypes(Node systemConfigNode) {
        SystemNode taskTypeNode = new SystemNode(systemConfigNode, "Setup Task Types", "The root node for all task types");
        m_nodeLocal.createNode(m_keyLocal, taskTypeNode);

        SystemNode stateNode = new SystemNode(systemConfigNode, "Setup States", "The root node for all states");
        m_nodeLocal.createNode(m_keyLocal, stateNode);

        SystemNode statusNode = new SystemNode(systemConfigNode, "Setup Statuses", "The root node for all statuses");
        m_nodeLocal.createNode(m_keyLocal, statusNode);

        State startState = new State(stateNode, "Start State", "Entry point for all state machines", false);
        m_stateLocal.createState(m_keyLocal, startState);

        Status defaultStatus = new Status(statusNode, "Default Status", "Default status for all state machines");
        m_statusLocal.createStatus(m_keyLocal, defaultStatus);

        State pendingState = new State(stateNode, "Pending", "The task has been created but not yet started.", false);
        m_stateLocal.createState(m_keyLocal, pendingState);

        State activeState = new State(stateNode, "Active", "The task is currently active.", true);
        m_stateLocal.createState(m_keyLocal, activeState);

        State inactiveState = new State(stateNode, "Inactive", "The task is not currently active.", false);
        m_stateLocal.createState(m_keyLocal, inactiveState);
        
        Status newStatus = new Status(statusNode, "New", "The task is new and has just left the start state.");
        m_statusLocal.createStatus(m_keyLocal, newStatus);

        Status startedStatus = new Status(statusNode, "Started", "The task has left the Pending state and entered the Active state.");
        m_statusLocal.createStatus(m_keyLocal, startedStatus);

        Status abandonedStatus = new Status(statusNode, "Abandoned", "The task has reverted to the Start State without being completed.");
        m_statusLocal.createStatus(m_keyLocal, abandonedStatus);

        Status suspendedStatus = new Status(statusNode, "Suspended", "The task has been moved from the Active to the Inactive state.");
        m_statusLocal.createStatus(m_keyLocal, suspendedStatus);

        Status resumedStatus = new Status(statusNode, "Resumed", "The task has been moved from the Inactive to the Active state.");
        m_statusLocal.createStatus(m_keyLocal, resumedStatus);

        Status completedStatus = new Status(statusNode, "Completed", "The task has reverted to the Start State after being successfully completed.");
        m_statusLocal.createStatus(m_keyLocal, completedStatus);

        TaskStateMachine defaultTaskType = new TaskStateMachine(taskTypeNode, "Default", "Default Task Type, should be changed during moderation");
        m_taskStateMachineLocal.createTaskStateMachine(m_keyLocal, defaultTaskType);

        TaskStateMachine basicTaskType = new TaskStateMachine(taskTypeNode, "Basic Task", "This state machine can typically be used for projects and actions.");
        m_taskStateMachineLocal.createTaskStateMachine(m_keyLocal, basicTaskType);

/*        Collection<Transition> transitions = new ArrayList<Transition>();
        Transition transition1 = new Transition(false, false);
        transition1.setId(basicTaskType.getStartTransition().getId());
        transition1.setSourceState(startState);
        transition1.setDestinationState(pendingState);
        transition1.setStatus(newStatus);
        transition1.setOwnerResponsible(false);
        transition1.setUserResponsible(false);
        transitions.add(transition1);

        Transition transition2 = new Transition(false, false);
        transition2.setSourceState(pendingState);
        transition2.setDestinationState(startState);
        transition2.setStatus(abandonedStatus);
        transitions.add(transition2);

        Transition transition3 = new Transition(false, false);
        transition3.setSourceState(pendingState);
        transition3.setDestinationState(activeState);
        transition3.setStatus(startedStatus);
        transitions.add(transition3);

        Transition transition4 = new Transition(false, false);
        transition4.setSourceState(activeState);
        transition4.setDestinationState(inactiveState);
        transition4.setStatus(suspendedStatus);
        transitions.add(transition4);

        Transition transition5 = new Transition(false, false);
        transition5.setSourceState(activeState);
        transition5.setDestinationState(startState);
        transition5.setStatus(completedStatus);
        transitions.add(transition5);

        Transition transition6 = new Transition(false, false);
        transition6.setSourceState(inactiveState);
        transition6.setDestinationState(activeState);
        transition6.setStatus(resumedStatus);
        transitions.add(transition6);

        Transition transition7 = new Transition(false, false);
        transition7.setSourceState(inactiveState);
        transition7.setDestinationState(startState);
        transition7.setStatus(abandonedStatus);
        transitions.add(transition7);
        
        basicTaskType.setTransitions(transitions);
        m_taskStateMachineLocal.editTaskStateMachine(m_keyLocal, basicTaskType);*/
    }

    private void setupContactDetailTypes(Node systemConfigNode) {
        ContactDetailTypes contactNode = new ContactDetailTypes(systemConfigNode);
        m_nodeLocal.createNode(m_keyLocal, contactNode);
    }

    private void setupSystemProperties(Node systemConfigNode) {
        SystemProperties propertyNode = new SystemProperties(systemConfigNode);
        m_nodeLocal.createNode(m_keyLocal, propertyNode);
    }

    private void setupLicenceManagement(Node systemConfigNode) {
        LicenceManagement licenceNode = new LicenceManagement(systemConfigNode);
        m_nodeLocal.createNode(m_keyLocal, licenceNode);
    }

    private void setupLoginHistory(Node systemConfigNode) {
        LoginHistory loginNode = new LoginHistory(systemConfigNode);
        m_nodeLocal.createNode(m_keyLocal, loginNode);
    }

    private void setupDirectory(Node systemConfigNode, Node directoryNode) {
        m_treeLocal.moveChildNode(m_keyLocal, systemConfigNode, directoryNode);
    }

}
